<!doctype html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">
  <script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
  <script src="wct-browser-config.js"></script>
  <script src="../../node_modules/wct-browser-legacy/browser.js"></script>
  <script type="module" src="../../lib/utils/async.js"></script>
</head>
<body>

<script type="module">
import { microTask, timeOut, animationFrame, idlePeriod } from '../../lib/utils/async.js';
var capturedErrors = [];
var captureEnabled = false;
window.addEventListener('error', function(e) {
  if (captureEnabled) {
    capturedErrors.push(e);
    e.stopImmediatePropagation();
    e.preventDefault();
    return true;
  }
});

suite('Queueing micro tasks', function() {

  test('The queued function runs.', function(done) {
    var callCount = 0;
    var callback = function() {
      callCount++;
    };

    microTask.run(callback);
    setTimeout(function() {
      assert.equal(callCount, 1);
      done();
    });
  });

  test('Functions queued multiple times are run multiple times.',
    function(done) {
      var callCount = 0;
      var callback = function() {
        callCount++;
      };
      microTask.run(callback);
      microTask.run(callback);
      microTask.run(callback);
      setTimeout(function() {
        assert.equal(callCount, 3);
        done();
      });
    });

  test('All queued functions are run.', function(done) {
    var callCount1 = 0;
    var callCount2 = 0;
    var callCount3 = 0;
    var callback1 = function() {
      callCount1++;
    };
    var callback2 = function() {
      callCount2++;
    };
    var callback3 = function() {
      callCount3++;
    };
    microTask.run(callback1);
    microTask.run(callback2);
    microTask.run(callback3);
    setTimeout(function() {
      assert.equal(callCount1, 1);
      assert.equal(callCount2, 1);
      assert.equal(callCount3, 1);
      done();
    });
  });

  test('Errors are thrown but the queue is continued', function(done) {
    captureEnabled = true;
    var callCount1 = 0;
    var callCount2 = 0;
    var callCount3 = 0;
    var callCount4 = 0;
    var callCount5 = 0;
    var callback1 = function() {
      callCount1++;
    };
    var callback2 = function() {
      callCount2++;
      throw new Error('intentional error 1');
    };
    var callback3 = function() {
      callCount3++;
      microTask.run(callback5);
      throw new Error('intentional error 2');
    };
    var callback4 = function() {
      callCount4++;
    };
    var callback5 = function() {
      callCount5++;
    };
    microTask.run(callback1);
    microTask.run(callback2);
    microTask.run(callback3);
    microTask.run(callback4);
    // All callbacks have been called by the next task.
    setTimeout(function() {
      captureEnabled = false;
      assert.equal(callCount1, 1);
      assert.equal(callCount2, 1);
      assert.equal(callCount3, 1);
      assert.equal(callCount4, 1);
      assert.equal(callCount5, 1);
      assert.equal(capturedErrors.length, 2);
      capturedErrors.length = 0;
      done();
    }, 100);
  });
});

suite('Cancelling micro tasks', function() {

  test('Queued functions are cancelable.', function(done) {
    var callCount = 0;
    var callback = function() {
      callCount++;
    };
    var asyncRef = microTask.run(callback);
    microTask.cancel(asyncRef);
    setTimeout(function() {
      assert.equal(callCount, 0);
      done();
    });
  });

  test('Multiple instances of the same queued function are cancelable.', function(done) {
    var callCount = 0;
    var callback = function() {
      callCount++;
    };
    var asyncRefs = [];
    asyncRefs.push(microTask.run(callback));
    asyncRefs.push(microTask.run(callback));
    asyncRefs.push(microTask.run(callback));
    microTask.cancel(asyncRefs.pop());
    microTask.cancel(asyncRefs.pop());
    microTask.cancel(asyncRefs.pop());
    setTimeout(function() {
      assert.equal(callCount, 0);
      done();
    });
  });

  test('Multiple queued functions are individually cancelable.', function(done) {
    var callCount1 = 0;
    var callCount2 = 0;
    var callCount3 = 0;
    var callback1 = function() {
      callCount1++;
    };
    var callback2 = function() {
      callCount2++;
    };
    var callback3 = function() {
      callCount3++;
    };
    var asyncRef1 = microTask.run(callback1);
    microTask.run(callback2);
    var asyncRef3 = microTask.run(callback3);
    microTask.cancel(asyncRef1);
    microTask.cancel(asyncRef3);
    setTimeout(function() {
      assert.equal(callCount1, 0);
      assert.equal(callCount2, 1);
      assert.equal(callCount3, 0);
      done();
    });
  });

});

suite('Queueing timeouts with a wait time', function() {

  test('The queued function runs.', function(done) {
    var callCount = 0;
    var callback = function() {
      callCount++;
    };
    timeOut.run(callback, 50);
    timeOut.after(50).run(callback);
    setTimeout(function() {
      assert.equal(callCount, 2);
      done();
    }, 100);
  });

  test('Functions queued multiple times are run multiple times.', function(done) {
    var callCount = 0;
    var callback = function() {
      callCount++;
    };
    timeOut.run(callback, 50);
    timeOut.after(50).run(callback);
    timeOut.run(callback, 50);
    timeOut.after(50).run(callback);
    timeOut.run(callback, 200);
    timeOut.after(200).run(callback);
    setTimeout(function() {
      assert.equal(callCount, 4);
      done();
    }, 100); // Only wait until the first two callbacks are called.
  });

  test('All queued functions are run.', function(done) {
    var callCount1 = 0;
    var callCount2 = 0;
    var callCount3 = 0;
    var callback1 = function() {
      callCount1++;
    };
    var callback2 = function() {
      callCount2++;
    };
    var callback3 = function() {
      callCount3++;
    };
    timeOut.run(callback1, 50);
    timeOut.run(callback2, 50);
    timeOut.run(callback3, 200);
    setTimeout(function() {
      assert.equal(callCount1, 1);
      assert.equal(callCount2, 1);
      assert.equal(callCount3, 0);
      done();
    }, 100); // Only wait until the first two callbacks are called.
  });

});

suite('Cancelling timers queued with a wait time', function() {

  test('Queued functions are cancelable.', function(done) {
    var callCount = 0;
    var callback = function() {
      callCount++;
    };
    var asyncRef = timeOut.run(callback, 50);
    timeOut.cancel(asyncRef);
    setTimeout(function() {
      assert.equal(callCount, 0);
      done();
    }, 100);
  });

  test('Multiple instances of the same queued function are cancelable.', function(done) {
    var callCount = 0;
    var callback = function() {
      callCount++;
    };
    var queue = [];
    queue.push(timeOut.run(callback, 50));
    queue.push(timeOut.run(callback, 50));
    queue.push(timeOut.after(50).run(callback));
    queue.push(timeOut.run(callback, 200));
    while (queue.length > 0) {
      timeOut.cancel(queue.pop());
    }
    setTimeout(function() {
      assert.equal(callCount, 0);
      done();
    }, 250);
  });

  test('Multiple queued functions are individually cancelable.', function(done) {
    var callCount1 = 0;
    var callCount2 = 0;
    var callCount3 = 0;
    var callback1 = function() {
      callCount1++;
    };
    var callback2 = function() {
      callCount2++;
    };
    var callback3 = function() {
      callCount3++;
    };
    var asyncRef1 = timeOut.run(callback1, 50);
    microTask.run(callback2, 50);
    var asyncRef3 = timeOut.run(callback3, 200);
    timeOut.cancel(asyncRef1);
    timeOut.cancel(asyncRef3);
    setTimeout(function() {
      assert.equal(callCount1, 0);
      assert.equal(callCount2, 1);
      assert.equal(callCount3, 0);
      done();
    }, 250);
  });

});

suite('Queueing functions to run before the next paint', function() {

  test('The queued function runs.', function(done) {
    var callback = sinon.spy();
    animationFrame.run(callback);
    requestAnimationFrame(function() {
      assert.isTrue(callback.calledOnce);
      done();
    });
  });

  test('Functions queued multiple times are run multiple times.', function(done) {
    var callback = sinon.spy();
    animationFrame.run(callback);
    animationFrame.run(callback);
    requestAnimationFrame(function() {
      assert.isTrue(callback.calledTwice);
      done();
    });
  });

  test('All queued functions are run.', function(done) {
    var callback1 = sinon.spy();
    var callback2 = sinon.spy();
    animationFrame.run(callback1);
    animationFrame.run(callback2);
    requestAnimationFrame(function() {
      assert.isTrue(callback1.calledOnce);
      assert.isTrue(callback2.calledOnce);
      done();
    });
  });

});

suite('Cancelling functions queued to run before the next paint', function() {

  test('Queued functions are cancelable.', function(done) {
    var callback = sinon.spy();
    var asyncRef = animationFrame.run(callback);
    animationFrame.cancel(asyncRef);
    requestAnimationFrame(function() {
      assert.isFalse(callback.called);
      done();
    });
  });

  test('Multiple instances of the same queued function are cancelable.', function(done) {
    var callback = sinon.spy();
    var asyncRefs = [];
    asyncRefs.push(animationFrame.run(callback));
    asyncRefs.push(animationFrame.run(callback));
    asyncRefs.push(animationFrame.run(callback));
    animationFrame.cancel(asyncRefs.pop());
    animationFrame.cancel(asyncRefs.pop());
    animationFrame.cancel(asyncRefs.pop());
    requestAnimationFrame(function() {
      assert.isFalse(callback.called);
      done();
    });
  });

  test('Multiple queued functions are individually cancelable.', function(done) {
    var callback1 = sinon.spy();
    var callback2 = sinon.spy();
    var callback3 = sinon.spy();
    var asyncRef1 = animationFrame.run(callback1, 50);
    animationFrame.run(callback2, 50);
    var asyncRef3 = animationFrame.run(callback3, 200);
    animationFrame.cancel(asyncRef1);
    animationFrame.cancel(asyncRef3);
    requestAnimationFrame(function() {
      assert.isFalse(callback1.called);
      assert.isTrue(callback2.calledOnce);
      assert.isFalse(callback3.called);
      done();
    });
  });

});

suite('Queueing functions to run during idle period', function() {
  var whenIdle = window.requestIdleCallback ? window.requestIdleCallback : function(fn) { setTimeout(fn, 16); };

  test('The queued function runs.', function(done) {
    var callback = sinon.spy();
    idlePeriod.run(callback);
    whenIdle(function() {
      assert.isTrue(callback.calledOnce);
      done();
    });
  });

  test('Functions queued multiple times are run multiple times.', function(done) {
    var callback = sinon.spy();
    idlePeriod.run(callback);
    idlePeriod.run(callback);
    whenIdle(function() {
      assert.isTrue(callback.calledTwice);
      done();
    });
  });

  test('All queued functions are run.', function(done) {
    var callback1 = sinon.spy();
    var callback2 = sinon.spy();
    idlePeriod.run(callback1);
    idlePeriod.run(callback2);
    whenIdle(function() {
      assert.isTrue(callback1.calledOnce);
      assert.isTrue(callback2.calledOnce);
      done();
    });
  });

});


suite('Cancelling functions queued to run during idle period', function() {
  var whenIdle = window.requestIdleCallback ? window.requestIdleCallback : function(fn) { setTimeout(fn, 16); };

  test('Queued functions are cancelable.', function(done) {
    var callback = sinon.spy();
    var asyncRef = idlePeriod.run(callback);
    idlePeriod.cancel(asyncRef);
    whenIdle(function() {
      assert.isFalse(callback.called);
      done();
    });
  });

  test('Multiple instances of the same queued function are cancelable.', function(done) {
    var callback = sinon.spy();
    var asyncRefs = [];
    asyncRefs.push(idlePeriod.run(callback));
    asyncRefs.push(idlePeriod.run(callback));
    asyncRefs.push(idlePeriod.run(callback));
    idlePeriod.cancel(asyncRefs.pop());
    idlePeriod.cancel(asyncRefs.pop());
    idlePeriod.cancel(asyncRefs.pop());
    whenIdle(function() {
      assert.isFalse(callback.called);
      done();
    });
  });

  test('Multiple queued functions are individually cancelable.', function(done) {
    var callback1 = sinon.spy();
    var callback2 = sinon.spy();
    var callback3 = sinon.spy();
    var asyncRef1 = idlePeriod.run(callback1, 50);
    idlePeriod.run(callback2, 50);
    var asyncRef3 = idlePeriod.run(callback3, 200);
    idlePeriod.cancel(asyncRef1);
    idlePeriod.cancel(asyncRef3);
    whenIdle(function() {
      assert.isFalse(callback1.called);
      assert.isTrue(callback2.calledOnce);
      assert.isFalse(callback3.called);
      done();
    });
  });

});
</script>
</body>
</html>
